
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Diagnostics;
using System.Collections;
using System.Runtime.Remoting;
using System.Threading;
using System.Xml;

using Swaf.Call;
using Swaf.Container;

namespace Swaf.BizRule
{
	/// <summary>
	/// Summary description for BasicExecutor.
	/// </summary>
	public abstract class BasicExecutor : MarshalByRefObject, IRuleExecutor
	{
		protected static IRuleQueue s_ruleQueue;
		/// <summary>Maintains a mapping between rule names and XML <c>Element</c> objects that live
		/// in <c>s_ruleInfo</c>.
		/// </summary>
		//protected static IDictionary s_rules = new Hashtable();
		//This protected member should not be used directly in the code since it is instantiated via a "lazy load" mechanism.
		//Instead, you should use the rules property.
		protected static IDictionary s_rules ;
		protected static IDictionary s_startupRules;
		/// <summary>This is the base document for the rule infomation nodes read during 
		/// configuration.
		/// </summary>
		protected static XmlDocument s_ruleInfo;
		protected static bool s_isStartup=false;
		protected static string s_ruleContextType = "BasicRuleContext";
		//When set, s_dynamicRuleRegister provides the ability to dynamically register requested rules
		//  this ICall implementation should return a BasicRuleInfo instance defining the rule to register.
		protected static ICall s_dynamicRuleRegister=null;
		public static string CACHE_THREAD_SLOT_NAME="SwafWorkerThreadCache";

		public BasicExecutor()
		{
			//
			// TODO: Add constructor logic here
			//
		}

		public static IRuleQueue RuleQueue{get{return s_ruleQueue;}}
		#region IRuleExecutor Members

		/// <summary>
		/// Execute a single business rule, returning the results after the rule has
		/// completed.</summary>
		/// <param name="ruleName">The known rule name for the business rule to be executed
		/// </param>
		/// <param name="callParams">The parameter to the business rule as the type needed by the
		/// rule.  Each rule can take a different .NET type and it is up to the caller
		/// to know what the called business rule needs for its data.</param>
		/// <returns>The results of the call</returns>
		public virtual IResults execute(string ruleName, params object[] callParams)
		{
			if((callParams != null) && callParams.Length == 1 && callParams[0] is object[])
				callParams = (object[])callParams[0];
			return execute(createNewContext(), ruleName, callParams);
		}

		public IResults simpleExecute(string ruleName, params object[] callParams)
		{
			return execute(ruleName, callParams);
		}
		
		/// <summary> Execute a single business rule within the passed in context, returning after
		/// the rule has completed.</summary>
		/// <remarks>
		/// This method is meant to be called by business rules as a way to share context
		/// with a group of business rules in order to colaborate in a (potentially) 
		/// parallel fashion in a common context.</remarks>
		/// <param name="ruleName"></param>
		/// <param name="context"></param>
		/// <param name="callParams"></param>
		/// <returns></returns>
		public virtual IResults execute(IRuleContext context, string ruleName, params object[] callParams)
		{
			BasicRuleInfo ri = ruleInfo(ruleName);
			return execute(context, ri, callParams);
		}

		protected virtual IResults execute(IRuleContext context, BasicRuleInfo ruleInfo, params object[] callParams)
		{
			try
			{
				bool executedNow = true;
				SingleCallAndResult callInfo = getRuleInfo(ruleInfo);
				IRuleGroup group = s_ruleQueue.enqueue(ruleInfo, callInfo, context, ParentRuleGroup, callParams, ref executedNow);
				if(!executedNow)
					group.waitForCompletion(false);
				return callInfo;
			}
			catch (Exception e)
			{
				return new BasicResults(e);
			}
		}
		
		/// <summary>
		/// Execute a single business rule with a callback object/method that will be 
		/// called with the results of the rule call when the rule has completed.</summary>
		/// <remarks>
		/// This is an asynchronous version of the <c>execute</c> method, allowing the
		/// client caller to do other things while the rule is executing.
		/// <p>
		/// It is possible to pass in <c>null</c> for the callback object and method
		/// and to use the IRuleGroup object returned to poll for the completion of the
		/// rule.
		/// </p></remarks>
		/// <param name="ruleName"></param>
		/// <param name="callback"></param>
		/// <param name="callbackMethod"></param>
		/// <param name="callParams"></param>
		/// <returns></returns>
		public virtual IRuleGroup executeAsync(string ruleName, object callback, string callbackMethod, object callParams)
		{
			return executeAsync(createNewContext(), ruleName, callback, callbackMethod, callParams);
		}

		public virtual IRuleGroup executeAsync(IRuleContext context, string ruleName, object callback, string callbackMethod, object callParams)
		{
			try
			{
				//If the thread calling this method has a CallbackQueue registered in its thread local storage
				//It will repackage the callback info into a CallbackQueue call so that when the rule completes
				//and the callback occurs, it will do the call through the callback queue mechanism.
				ICall cb = null;
				if(callback != null)
				{
					cb = new ObjectCall(callback, callbackMethod);
					ICallbackQueue cbqueue = (ICallbackQueue)Thread.GetData(Thread.GetNamedDataSlot("CallbackQueue"));
					if(cbqueue != null)
						cb = new CallbackQueueCall(cbqueue, cb);
				}

				ICallResults rc = null;
				//------------------------------------------------------------
				//HACK: 1
				//This is a backdoor pass that says, if you pass a SingleCallAndResult as a callParam that has
				//CallInfo defined, it will use the CallInfo as the rule, and take the results and make it
				//the callParams.  This is a way for a Class that is using an instance of this class internally
				//and wants to override the creation of the that.  This is needed by the ThreadedRuleQueue mechanism
				//to maintain results for a rule group properly.  Search on HACK: 1 for all code needed to make this
				//work for the framework.
				if(callParams is SingleCallAndResult)
				{
					SingleCallAndResult r = (SingleCallAndResult)callParams;
					if(r.CallInfo != null)
					{
						callParams = r.CallResults;
						r.setResults(null,null);
						rc = r;
					}
				}
				//------------------------------------------------------------
				if(rc == null)
					rc = getRuleInfo(ruleName);

				return s_ruleQueue.enqueue(ruleInfo(ruleName), rc, context, ParentRuleGroup, cb, callParams);
			}
			catch (Exception)
			{
				throw;
			}
		}
		
		/// <summary>
		/// Executes multiple rules (potentially) in parallel, returning after all the
		/// rules specified have executed.</summary>
		/// <remarks>
		/// The <c>IResults</c> returned from this call will have the same number of
		/// sub-results in it as the number of rules specified.  It is this <i>multi-rule</i>
		/// execution that make the <c>RuleManager</c> so powerful.</remarks>
		/// <param name="ruleNames"></param>
		/// <param name="callParams"></param>
		/// <returns></returns>
		public virtual IResults execute(string[] ruleNames, object[] callParams)
		{
			Debug.Assert(ruleNames.Length > 0);
			IRuleContext ctx = createNewContext();
			int numRules = ruleNames.Length;
			IRuleGroup group = s_ruleQueue.enqueue(ruleInfo(ruleNames[0]), getRuleInfo(ruleNames[0]), ctx, ParentRuleGroup, null, callParams[0]);
			for(int pos = 1; pos < numRules; ++pos)
				group.execute(ctx, ruleNames[pos], null, "", callParams[pos]);
			group.waitForCompletion(false);
			return group;
		}

		public IResults execute(string ruleName, ICollection callParams, bool multiCall)
		{
			return execute(createNewContext(), ruleName, callParams, multiCall);
		}

		public IResults execute(IRuleContext context, string ruleName, ICollection callParams, bool multiCall)
		{
			ICallResults rule = getRuleInfo(ruleName);
			bool executedNow = true;

			if(rule.CallInfo.WorksWithMultiCall)
			{
				IRuleGroup group = s_ruleQueue.enqueue(ruleInfo(ruleName), rule, context, ParentRuleGroup, new MultiCallInfo(callParams), ref executedNow);
				if(!executedNow)
					group.waitForCompletion(false);
				return (IResults)group;
			}
			else
			{
				executedNow = false;
				IRuleGroup group = null;
				foreach(object param in callParams)
					if(group == null)
						group = s_ruleQueue.enqueue((IRuleInfo)rules[ruleName], rule, context, ParentRuleGroup, param, ref executedNow);
					else
						group.execute(context, ruleName, null, null, param);
				group.waitForCompletion(false);
				return group;
			}
		}


		public virtual IResults execute(IRuleContext context, string[] ruleNames, object[] callParams)
		{
			IRuleGroup group = context.RuleGroup.createChildGroup(true);
			int numRules = ruleNames.Length;
			for(int pos = 0; pos < numRules; ++pos)
			{
				group.execute(context,ruleNames[pos], null, "", callParams[pos]);
			}
			group.waitForCompletion(false);
			return group;
		}
		
		/// <summary>
		/// Executes multiple business rules with a callback object/method that will be 
		/// called with the resultswhen all the rules specified have completed.</summary>
		/// <remarks>
		/// This is an asynchronous version of the <c>execute</c> method, allowing the
		/// client caller to do other things while the multiple rules are executing.
		/// <p>
		/// It is possible to pass in <c>null</c> for the callback object and method
		/// and to use the IRuleGroup object returned to poll for the completion of the
		/// rules.</p></remarks>
		/// <param name="ruleName"></param>
		/// <param name="callParams"></param>
		/// <param name="callback"></param>
		/// <param name="callbackMethod"></param>
		/// <returns></returns>
		public virtual IRuleGroup[] executeAsync(string[] ruleName, object[] callParams, object callback, string callbackMethod)
		{
			//TODO: Impl method
			return null;
		}


		public IRuleGroup executeAsync(string ruleCallInfo, object callback, string callbackMethod)
		{
			return executeAsync("executeBizRules", callback, callbackMethod, ruleCallInfo);
		}

		public IResults execute(string ruleCallInfo)
		{
			BasicRuleInfo ri = ruleInfo(ruleCallInfo, false);
			if (ri == null)
			{
				return CallRulesBizRule.execute(App, createNewContext(), ruleCallInfo);
			}
			else
			{
				//A rule is being called with no parameters.
				return execute(createNewContext(), ri);
			}
		}

		public IResults remoteExecute(string ruleName, string remoteUrl, params object[] callParams)
		{
			RemotingRuleMgrCall reca = new RemotingRuleMgrCall(remoteUrl, ruleName,false);
			IResults rc = reca.call(new object[0]) as IResults;
			return rc;
		}

		public IRuleGroup remoteExecuteAsync(string ruleName, string remoteUrl, object callback, string callbackMethod, params object[] callParams)
		{
			try
			{
				//If the thread calling this method has a CallbackQueue registered in its thread local storage
				//It will repackage the callback info into a CallbackQueue call so that when the rule completes
				//and the callback occurs, it will do the call through the callback queue mechanism.
				ICall cb = null;
				if (callback != null)
				{
					cb = new ObjectCall(callback, callbackMethod);
					ICallbackQueue cbqueue = (ICallbackQueue)Thread.GetData(Thread.GetNamedDataSlot("CallbackQueue"));
					if (cbqueue != null)
						cb = new CallbackQueueCall(cbqueue, cb);
				}

				ICallResults rc = null;
				////------------------------------------------------------------
				////HACK: 1
				////This is a backdoor pass that says, if you pass a SingleCallAndResult as a callParam that has
				////CallInfo defined, it will use the CallInfo as the rule, and take the results and make it
				////the callParams.  This is a way for a Class that is using an instance of this class internally
				////and wants to override the creation of the that.  This is needed by the ThreadedRuleQueue mechanism
				////to maintain results for a rule group properly.  Search on HACK: 1 for all code needed to make this
				////work for the framework.
				//if (callParams is SingleCallAndResult)
				//{
				//    SingleCallAndResult r = (SingleCallAndResult)callParams;
				//    if (r.CallInfo != null)
				//    {
				//        callParams = r.CallResults;
				//        r.setResults(null, null);
				//        rc = r;
				//    }
				//}
				////------------------------------------------------------------
				if (rc == null)
					rc = getRuleInfo(ruleName);

				BasicRuleInfo ri = new BasicRuleInfo(new RemotingRuleMgrCall(remoteUrl, ruleName, true), ruleName, CallDistanceType.wan);
				return s_ruleQueue.enqueue(ri, new SingleCallAndResult(ri.CallInfo), createNewContext(), ParentRuleGroup, cb, callParams);
			}
			catch (Exception)
			{
				throw;
			}
		}

		public IResults remoteExecute(string[] ruleNames, string remoteUrl, params object[][] callParams)
		{
			//TODO: Implement
			return null;
		}


		#endregion
		
		/// <summary>Gets the ICall object based on the specified rule name.</summary>
		/// <param name="ruleName"></param>
		/// <returns></returns>
		protected internal virtual SingleCallAndResult getRuleInfo(string ruleName)
		{
			BasicRuleInfo rInfo = ruleInfo(ruleName);
			ICall callInfo = rInfo.CallInfo;
			if (callInfo != null)
			{
				return new SingleCallAndResult(callInfo);
			}
			else
			{
                throw new BadRuleNameException(App.resMgr.getMessage("getRuleInfo.string.ruleName", ruleName));
			}
		}

		protected internal virtual SingleCallAndResult getRuleInfo(BasicRuleInfo rule)
		{
			ICall callInfo = rule.CallInfo;
			if (callInfo != null)
			{
				return new SingleCallAndResult(callInfo);
			}
			else
			{
                throw new BadRuleNameException(App.resMgr.getMessage("getRuleInfo.BasicRuleInfo.ruleName", rule.Name));
			}
		}
		
		protected BasicRuleInfo ruleInfo(string ruleName, bool throwException)
		{
		    Exception dynamicException = null;
		    
			BasicRuleInfo info =(BasicRuleInfo)rules[ruleName];
			if (info == null && s_dynamicRuleRegister != null)
			{
			    try
			    {
                    info = (BasicRuleInfo)s_dynamicRuleRegister.call(ruleName);
                }
			    catch (Exception e)
			    {
			        dynamicException = e;
			    }
			}

		    if (info == null && throwException)
		    {
                if (dynamicException != null)
                {
                    throw new Swaf.BizRule.BadRuleNameException(string.Format("Request rule information was not found for rule: {0}", ruleName), dynamicException);
                }
                else
                {
                    throw new Swaf.BizRule.BadRuleNameException(string.Format("Request rule information was not found for rule: {0}", ruleName));
                }
		    }

		    return info;
		}
		
        protected BasicRuleInfo ruleInfo(string ruleName)
        {
            return ruleInfo(ruleName, true);
        }

        /// <summary>
        /// This property provides access to the s_rules dictionary.  This should be used rather than direct access to the
        /// dictionary because this dictionary is instantiated using a "lazy load" paradigm.
        /// </summary>
        internal IDictionary rules
        {
            get 
            {
                if (s_isStartup)
                {
                    if (s_startupRules == null)
                    {
                        s_startupRules = new Hashtable();
                    }
                    return s_startupRules;
                }
                else
                {
                    if (s_rules == null)
                    {
                        s_rules = (IDictionary) Application.currentApp.factories[@"Dictionaries"].createFromNickName(@"rule_versioning");
                        //s_rules = new Hashtable();
                        if (s_startupRules != null)
                        {
                            foreach(DictionaryEntry entry in s_startupRules)
                            {
                                addRuleDef((string)entry.Key,  (IRuleInfo) entry.Value, s_rules);
                                //s_rules.Add(entry.Key,  entry.Value);
                            }
                            s_startupRules.Clear();
                        }
                    }
                    return s_rules;
                }
            }
        }
        internal void addRuleDef(string key, IRuleInfo info, IDictionary map)
        {
            if (info is BasicRuleInfo && map is INameAddressSupport)
            {
                string expire = ((BasicRuleInfo) info).Expiration;
                if (expire != null)
                    ((INameAddressSupport) map).put(key, info,expire);
                else
                    map[key]= info;
            }
            else
            {
                map[key]=info;
            }
        }
        private static Type[] appType = new Type[]{typeof(IApplication)};
        protected IRuleContext createNewContext()
        {
            return (IRuleContext)Cmn.callCtor(s_ruleContextType, appType, new object[]{App});
        }
        protected abstract IApplication App{get;}
        protected virtual IRuleGroup ParentRuleGroup{get{return null;}}
	}
}
